/**
 * 数组变化监听器回调函数类型
 * @template T 数组元素类型
 * @param newArray 变化后的新数组
 * @param oldArray 变化前的旧数组
 * @param changeType 变化类型，可以是以下值：
 *   - 'index-update' - 通过索引更新元素
 *   - 'length-change' - 数组长度变化
 *   - 'method:push' - 使用 push 方法
 *   - 'method:pop' - 使用 pop 方法
 *   - 'method:shift' - 使用 shift 方法
 *   - 'method:unshift' - 使用 unshift 方法
 *   - 'method:splice' - 使用 splice 方法
 *   - 'method:sort' - 使用 sort 方法
 *   - 'method:reverse' - 使用 reverse 方法
 *   - 'reassign' - 整个数组被重新赋值
 * @example 
 * // 使用示例
  const observableArray = new ObservableArray<number>([1, 2, 3])

  // 添加变化监听器
  const listener: ArrayChangeListener<number> = (
    newArray,
    oldArray,
    changeType
  ) => {
    console.log('--- 数组变化 ---')
    console.log(`变化类型: ${changeType}`)
    console.log(`旧数组: [${oldArray}]`)
    console.log(`新数组: [${newArray}]`)
    console.log('')
  }
  observableArray.onChange(listener)
 */
type ArrayChangeListener<T> = (
  newArray: T[],
  oldArray: T[],
  changeType: string,
) => void

export class ObservableArray<T> {
  private _value: T[]
  private listeners: ArrayChangeListener<T>[] = []
  private static readonly MUTATION_METHODS = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "sort",
    "reverse",
  ] as const
  private isBatching = false
  private pendingNotification: {
    newArray: T[] | null
    oldArray: T[] | null
    changeType: string | null
  } = { newArray: null, oldArray: null, changeType: null }

  constructor(initialArray: T[] = []) {
    this._value = this.createProxy([...initialArray])
  }

  /**
   * 获取代理后的数组值
   */
  public get value(): T[] {
    return this._value
  }

  /**
   * 设置新数组值（会触发重新赋值监听）
   */
  public set value(newValue: T[]) {
    const oldValue = [...this._value]
    this._value = this.createProxy([...newValue])
    this.notifyListeners([...this._value], oldValue, "reassign")
  }

  /**
   * 添加数组变化监听器
   * @param listener 监听回调函数
   */
  public onChange(listener: ArrayChangeListener<T>): void {
    this.listeners.push(listener)
  }

  /**
   * 移除数组变化监听器
   * @param listener 要移除的监听回调函数
   */
  public removeListener(listener: ArrayChangeListener<T>): void {
    const index = this.listeners.indexOf(listener)
    if (index !== -1) this.listeners.splice(index, 1)
  }

  private createProxy(array: T[]): T[] {
    const instrumentations = this.createMethodInstrumentations()

    return new Proxy(array, {
      get: (target, prop, receiver) => {
        if (typeof prop === "string" && instrumentations.has(prop)) {
          return instrumentations.get(prop)
        }
        return Reflect.get(target, prop, receiver)
      },
      set: (target, prop, value, receiver) => {
        const oldArray = [...target]
        const numericKey = Number(prop)

        // 判断是否是索引赋值
        const isIndexUpdate = !isNaN(numericKey) && numericKey < target.length

        const success = Reflect.set(target, prop, value, receiver)
        if (success) {
          // 触发索引修改的监听
          if (isIndexUpdate) {
            this.notifyListeners([...target], oldArray, "index-update")
          }
          // 触发length变化的监听
          else if (prop === "length") {
            this.notifyListeners([...target], oldArray, "length-change")
          }
        }
        return success
      },
    })
  }

  private createMethodInstrumentations(): Map<string, Function> {
    const instrumentations = new Map<string, Function>()

    ObservableArray.MUTATION_METHODS.forEach((method) => {
      instrumentations.set(method, (...args: any[]) => {
        const oldArray = [...this._value]

        // 开始批量操作
        this.isBatching = true
        const originalMethod = Array.prototype[method as any]
        const result = originalMethod.apply(this._value, args)
        this.isBatching = false

        // 发送批量通知
        if (this.pendingNotification.changeType) {
          this.notifyListeners([...this._value], oldArray, `method:${method}`)
          this.pendingNotification = {
            newArray: null,
            oldArray: null,
            changeType: null,
          }
        }

        return result
      })
    })

    return instrumentations
  }

  private notifyListeners(
    newArray: T[],
    oldArray: T[],
    changeType: string,
  ): void {
    if (this.isBatching) {
      // 在批量模式下，只保存最后一次状态
      this.pendingNotification = { newArray, oldArray, changeType }
      return
    }

    setTimeout(() => {
      this.listeners.forEach((listener) => {
        listener(newArray, oldArray, changeType)
      })
    }, 0)
  }
}
